package com.ashlikun.flutter_assets_gen.utils

import com.ashlikun.flutter_assets_gen.utils.PluginUtils.showNotify
import com.ashlikun.flutter_assets_gen.utils.PluginUtils.toLowCamelCase
import com.ashlikun.flutter_assets_gen.utils.PluginUtils.upperCaseFirst
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessModuleDir
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import com.intellij.psi.util.PsiTreeUtil
import io.flutter.utils.FlutterModuleUtils
import org.jetbrains.kotlin.idea.core.util.toPsiFile
import org.jetbrains.kotlin.idea.util.findModule
import org.jetbrains.yaml.YAMLElementGenerator
import org.jetbrains.yaml.psi.YAMLFile
import org.jetbrains.yaml.psi.YAMLMapping
import org.jetbrains.yaml.psi.YAMLSequence
import java.io.File

class FileGenerator(private val project: Project) {
    private val ignoreDir = listOf("2.0x", "3.0x", "Mx", "Nx")

    /**
     * 为所有模块重新生成
     */
    fun generateAll() {
        WriteCommandAction.runWriteCommandAction(project) {
            val assets = FileHelperNew.getAssets(project) as MutableList
            assets.removeAll {
                println("module : ${it.module} assets ${it.assetVFiles}")
                it.assetVFiles.isEmpty()
            }
            if (assets.isEmpty()) {
                showNotify("Please configure your assets path in pubspec.yaml")
                return@runWriteCommandAction
            }
            for (config in assets) {
                generateWithConfig(config)
            }
        }
    }


    /**
     * 生成单个模块文件
     */
    fun generateOne(config: ModulePubSpecConfig) {
        WriteCommandAction.runWriteCommandAction(project) {
            generateWithConfig(config)
        }
    }

    private fun generateWithConfig(config: ModulePubSpecConfig) {
        println(config)
        val module = config.module
        val map = mutableMapOf<String, String>()
        val ignorePath = FileHelperNew.getPathIgnore(config)
        // 这里可能有多个目录或者文件
        for (file in config.assetVFiles) {
            println("generate file map in $file")
            generateFileMap(
                file,
                config,
                map,
                ignorePath,
            )
        }
        if (map.isEmpty()) {
//            showNotify("assets path is empty")
            println("${config.module} assets map is empty, skip")
            return
        }
        val content = StringBuilder()
        content.append("///This file is automatically generated. DO NOT EDIT, all your changes would be lost.\n")
        val className = FileHelperNew.getGeneratedClassName(config)
        content.append("class $className {\n  $className._();\n\n")
        //插入map static const List<String> assetsMap = [Assets.aaa, Assets.bbb];
        content.append("  static const List<String> assetsMap = [")
        content.append(map.toSortedMap().keys.joinToString(", "))
        content.append("];\n\n")
        map.toSortedMap().forEach {
            content.append("  static const String ${it.key} = '${config.getLeadingWithPackageNameIfChecked()}${it.value}';\n")
        }
        content.append("\n}\n")
        val psiManager = PsiManager.getInstance(project)
        val psiDocumentManager = PsiDocumentManager.getInstance(project)
        FileHelperNew.getGeneratedFile(config).let { generated ->
            psiManager.findFile(generated)?.let { dartFile ->
                psiDocumentManager.getDocument(dartFile)?.let { document ->
                    if (document.text != content.toString()) {
                        document.setText(content)
                        psiDocumentManager.commitDocument(document)
                        showNotify("$module : assets generate succeed")
                    } else {
                        showNotify("$module : nothing changed")
                    }
                }
            }
        }
    }

    private fun generateFileMap(
        root: VirtualFile,
        config: ModulePubSpecConfig,
        map: MutableMap<String, String>,
        ignorePath: List<String>,
    ) {
        val namedWithParent = FileHelperNew.isNamedWithParent(config)
        val pattern = FileHelperNew.getFilenameSplitPattern(config)
        val basePath = config.module.guessModuleDir()?.path
        val regex = Regex(pattern)
        if (root.isDirectory) {
            root.children.filter {
                var pathIgnore = false
                if (ignorePath.isNotEmpty()) {
                    for (name in ignorePath) {
                        if (it.path.contains(name, ignoreCase = true)) {
                            pathIgnore = true
                            println("${it.path} pathIgnore : $pathIgnore")
                            break
                        }
                    }
                }
                !it.name.startsWith('.') && checkName(it.name) && !pathIgnore
            }.forEach {
                if (it.isDirectory) {
                    generateFileMap(it, config, map, ignorePath)
                } else {
                    config(it, regex, basePath, namedWithParent, map)
                }
            }
        } else {
            config(root, regex, basePath, namedWithParent, map)
        }
    }

    private fun config(
        it: VirtualFile,
        regex: Regex,
        basePath: String?,
        namedWithParent: Boolean,
        map: MutableMap<String, String>
    ) {
        var key = it.nameWithoutExtension.replace(".", "_").toLowCamelCase(regex)///fileName style
        val value = it.path.removePrefix("$basePath/")
        if (namedWithParent) {
            it.parent?.let { parent ->
                key = "${parent.name.toLowCamelCase(regex)}${key.upperCaseFirst()}"
                if (map.containsKey(key)) {
                    key = "${parent.parent.name.toLowCamelCase(regex)}${key.upperCaseFirst()}"
                }
                map[key] = value
            }
        } else {
            map[key] = value
        }
    }

    /**
     * 将所选择目录及子目录添加到yaml配置
     */
    fun buildYaml(file: VirtualFile) {
        saveChanges()
        val modules = FileHelperNew.getAssets(project)
        var module: ModulePubSpecConfig? = null
        for (m in modules) {
//            println("file.path ${file.path} pubRoot path ${m.pubRoot.path}")
            if (file.path.startsWith(m.pubRoot.path)) {
                module = m
                break
            }
        }
        if (module != null && FlutterModuleUtils.isFlutterModule(module.module)) {
//            println("current module is ${module.module}")
            val paths = mutableListOf<String>()
            val rootPath = "${module.pubRoot.path}/"
            if (file.isDirectory) {
                traversalDir(file, rootPath, paths)
            } else {
                paths.add(file.path.removePrefix(rootPath))
            }
//            println(paths)
            val moduleAssets = FileHelperNew.tryGetAssetsList(module.map)
            if (moduleAssets != null) {
                val moduleDir = file.findModule(project)?.guessModuleDir()
                println(moduleDir?.path)
                moduleAssets.removeIf {
                    var parentPath = moduleDir?.path
                    var path = it as String
                    path = path.removeSuffix(File.separator)
                    if (path.contains(File.separator)) {
                        val subIndex = path.lastIndexOf(File.separator)
                        parentPath = "$parentPath${File.separator}${path.substring(0, subIndex + 1)}"
                        path = path.substring(subIndex + 1, path.length)
//                        println("parentPath：$parentPath path：$path")
                    }
                    val asset = File(parentPath, path)
                    println("${asset.absolutePath} file exists : ${asset.exists()}")
                    !asset.exists()
                }
                paths.removeIf {
                    moduleAssets.contains(it)
                }
            }
            val yamlFile = module.pubRoot.pubspec.toPsiFile(project) as? YAMLFile
            yamlFile?.let {
                val psiElement =
                    yamlFile.node.getChildren(null)
                        .firstOrNull()?.psi?.children?.firstOrNull()?.children?.firstOrNull { it.text.startsWith("flutter:") }
                if (psiElement != null) {
                    val yamlMapping = psiElement.children.first() as YAMLMapping
                    WriteCommandAction.runWriteCommandAction(project) {
                        var assetsValue = yamlMapping.keyValues.firstOrNull { it.keyText == "assets" }
                        val stringBuilder = StringBuilder()
                        moduleAssets?.forEach {
                            stringBuilder.append("    - $it\n")
                        }
                        paths.forEach {
                            stringBuilder.append("    - $it\n")
                        }
                        stringBuilder.removeSuffix("\n")
                        if (assetsValue == null) {
                            assetsValue = YAMLElementGenerator.getInstance(project)
                                .createYamlKeyValue("assets", stringBuilder.toString())
                            yamlMapping.putKeyValue(assetsValue)
                        } else {
                            val yamlValue = PsiTreeUtil.collectElementsOfType(
                                YAMLElementGenerator.getInstance(project)
                                    .createDummyYamlWithText(stringBuilder.toString()), YAMLSequence::class.java
                            ).iterator().next()
                            assetsValue.setValue(yamlValue)
                        }
                    }
                }
            }
            saveChanges()
            showNotify("Flutter: Configuration complete.")
        } else {
            showNotify("This module is not flutter module")
        }
    }

    private fun saveChanges() {
        ApplicationManager.getApplication().saveAll()
        PsiDocumentManager.getInstance(project).commitAllDocumentsUnderProgress()
    }

    private fun traversalDir(file: VirtualFile, rootPath: String, list: MutableList<String>) {
        if (file.isDirectory) {
            list.add("${file.path.removePrefix(rootPath)}/")
            file.children.forEach {
                if (it.isDirectory) {
                    traversalDir(it, rootPath, list)
                }
            }
        }
    }

    private fun checkName(name: String): Boolean {
        return !ignoreDir.contains(name)
    }

}
